/*
 * timer.c
 *
 * This file contains the source code for the timed controller struct and
 * related functions.
 * The timed controller's purpose is to act as a secondary driver for events
 * that take place in game after a specified amount of time. When the given
 * amount of time passes, actions in the timed controller are added to the main
 * controller.
 *
 * The timed controller acts on a separate thread, created and handled
 * internally.
 */

#include <pthread.h>
#include <stdio.h>
#include <utils/error.h>
#include "controller.h"
#include "timer.h"

/*
 * The global timed controller object.
 */
struct timed_controller_object *controller;

/*
 * The timed controller thread.
 */
pthread_t timer_thread;

/*
 * The timed controller mutex.
 */
pthread_mutex_t timer_mutex;

/*
 * An indicator describing whether or not the timed controller is initialized
 * and running.
 */
int tc_initialized = 0;

/*
 * The loop for the timed controller thread. It will constantly check the head
 * of the queue to see if the timed action should occur or not, and if it should
 * it will be popped from the queue and added to the main controller.
 */
void* main_loop() {
    struct timed_controller_node *event;
    time_t timestamp;
    while( tc_initialized ) {
        time(&timestamp);
        if( controller->head != NULL && controller->head->delay < timestamp ) {
            pthread_mutex_lock(&timer_mutex);
            event = controller->head;
            controller->head = event->next;
            controller_add(event->act);
            pthread_mutex_unlock(&timer_mutex);
        }
    }
    pthread_exit(NULL);
    return 0;
}

/*
 * Creates the timed controller, creating the queue and worker thread.
 * Will print a warning message to stderr if timed controller is already
 * initialized.
 */
int timed_controller_create() {
    if( tc_initialized ) {
        printerr( WARNING_CONT_TREINIT );
        return 0;
    }
    else {
        controller = (struct timed_controller_object*)malloc(sizeof(struct
            timed_controller_object));
        controller->head = NULL;
        controller->tail = NULL;
        tc_initialized = 1;
        return pthread_create(&timer_thread, NULL, &main_loop, NULL);
    }
}

/*
 * Returns 1 (true) if the timed controller has been initialized and is able to
 * accept actions.
 */
int timed_controller_initialized() {
    return tc_initialized;
}

/*
 * Frees the timed controller structs and uninitializes.
 * Will print a warning message to stderr if timed controller is not
 * initialized.
 */
void timed_controller_free() {
    struct timed_controller_node *node;
    struct timed_controller_node *next;
    if( tc_initialized ) {
        tc_initialized = 0;
        node = controller->head;
        free(controller);
        while( node != NULL ) {
            next = node->next;
            free(node);
            node = next;
        }
        controller = NULL;
    }
    else {
        printerr( WARNING_CONT_TFREE );
    }
}

/*
 * Adds the given action to the timed controller queue, to be executed the given
 * delay (in seconds) after the current time the action was received.
 * Will print a warning message to stderr if timed controller is not
 * initialized.
 */
void timed_controller_add(action *a, unsigned int delay) {
    time_t current_time;
    struct timed_controller_node *new_event;
    struct timed_controller_node *i;
    if( tc_initialized ) {
        if( a != NULL ) {
            time(&current_time);
            new_event = (struct timed_controller_node*)malloc(sizeof(struct
                timed_controller_node));
            new_event->act = a;
            new_event->delay = delay + current_time;

            pthread_mutex_lock(&timer_mutex);
            if( controller->head == NULL ) { /* Queue's empty */
                new_event->next = NULL;
                new_event->previous = NULL;
                controller->head = new_event;
                controller->tail = NULL;
            }
            else if( controller->tail == NULL ) { /* Only 1 action in queue */
                if( controller->head->delay < new_event->delay ) {
                    new_event->next = NULL;
                    new_event->previous = controller->head;
                    controller->head->next = new_event;
                    controller->tail = new_event;
                }
                else {
                    new_event->next = controller->head;
                    new_event->previous = NULL;
                    controller->tail = controller->head;
                    controller->head = new_event;
                    new_event->next = controller->tail;
                }
            }
            else {
                if( controller->tail->delay < new_event->delay ) {
                    new_event->next = NULL;
                    new_event->previous = controller->tail;
                    controller->tail->next = new_event;
                    controller->tail = new_event;
                }
                else {
                    for( i = controller->tail; i != NULL &&
                        i->delay > new_event->delay; i = i->previous );
                    if( i == NULL ) { /* Hit the head of the queue */
                        new_event->next = controller->head;
                        new_event->previous = NULL;
                        controller->head->previous = new_event;
                        controller->head = new_event;
                    }
                    else {
                        new_event->next = i->next;
                        new_event->previous = i;
                        if( i->next != NULL ) {
                            i->next->previous = new_event;
                        }
                        i->next = new_event;
                    }
                }
            }
            pthread_mutex_unlock(&timer_mutex);
        }
    }
    else {
        printerr( WARNING_CONT_TADD );
    }
}
